﻿#region Header
// ------------------------ Licence / Copyright ------------------------
// 
// ScrumTable for Scrum Meeting Support
// Copyright © HSR - Hochschule für Technik Rapperswil
// All Rights Reserved
// 
// Author:
//  Michael Gfeller Silvan Gehrig Patrick Boos
// 
// ---------------------------------------------------------------------
#endregion

#region Usings

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows.Data;
using Microsoft.Practices.Unity;
using ScrumTable.BL.DM.DataManagement;
using ScrumTable.Common.Collections;
using ScrumTable.UI.ViewModel.Data;
using ScrumTable.UI.ViewModel.Extension;

#endregion

namespace ScrumTable.UI.ViewModel.View
{
    /// <summary>
    /// View model for the Project Planning Scrum Poker View
    /// </summary>
    public class ProjectPlanningUserStoriesViewModel : ViewModelBase
    {

        #region Declarations
        //--------------------------------------------------------------------
        // Declarations
        //--------------------------------------------------------------------
        
        public Range RangeMust
        {
            get { return ScrumData.ConfigManager.Config.RankingMust; }
        }


        public Range RangeShould
        {
            get { return ScrumData.ConfigManager.Config.RankingShould; }
        }

        public Range RangeCould
        {
            get { return ScrumData.ConfigManager.Config.RankingCould; }
        }
       
        public Range RangeNull = new Range(true);
        private ScrumViewModelData _scrumData;
        private ObservableCollection<IScrumItemViewModel<DomainBase>> _userStoriesMust;
        private ObservableCollection<IScrumItemViewModel<DomainBase>> _userStoriesShould;
        private ObservableCollection<IScrumItemViewModel<DomainBase>> _userStoriesCould;
        private ObservableCollection<IScrumItemViewModel<DomainBase>> _userStoriesNone;

        #endregion

        #region Properties
        //--------------------------------------------------------------------
        // Properties
        //--------------------------------------------------------------------  

        /// <summary>
        /// Gets or sets the scrum data.
        /// </summary>
        /// <value>The scrum data.</value>
        [Dependency]
        public ScrumViewModelData ScrumData
        {
            get
            {
                return _scrumData;
            }
            set
            {
                _scrumData = value;
                ScrumData.DataContext.ElementChanged += OnContextElementChanged;
                ScrumData.DataContext.CollectionChanged += OnContextCollectionChanged;
                ScrumData.PropertyChanged += OnScrumDataPropertyChanged;
            }
        }

        /// <summary>
        /// Gets the user stories for must priority.
        /// </summary>
        /// <value>The user stories for must priority.</value>
        public ObservableCollection<IScrumItemViewModel<DomainBase>> UserStoriesMust
        {
            get
            {

                var project = ScrumData.CurrentProject;
                if (project != null)
                {
                    return SetUserStoriesOnList(project,  ScrumData.ConfigManager.Config.RankingMust, ref _userStoriesMust, ScrumData);
                }
                return null;
            }
        }


        /// <summary>
        /// Gets the user stories for should priority.
        /// </summary>
        /// <value>The user stories for should priority.</value>
        public ObservableCollection<IScrumItemViewModel<DomainBase>> UserStoriesShould
        {
            get
            {
                var project = ScrumData.CurrentProject;
                if (project != null)
                {
                    return SetUserStoriesOnList(project, ScrumData.ConfigManager.Config.RankingShould, ref _userStoriesShould, ScrumData);
                }
                return null;
            }
        }


        /// <summary>
        /// Gets the user stories for could priority.
        /// </summary>
        /// <value>The user stories for could priority.</value>
        public ObservableCollection<IScrumItemViewModel<DomainBase>> UserStoriesCould
        {
            get
            {
                var project = ScrumData.CurrentProject;
                if (project != null)
                {
                    return SetUserStoriesOnList(project, ScrumData.ConfigManager.Config.RankingCould, ref _userStoriesCould, ScrumData);
                }
                return null;
            }
        }

        /// <summary>
        /// Gets the user stories with no priority.
        /// </summary>
        /// <value>The user stories with no priority.</value>
        public ObservableCollection<IScrumItemViewModel<DomainBase>> UserStoriesNone
        {
            get
            {
                var project = ScrumData.CurrentProject;
                if (project != null)
                {

                    return SetUserStoriesOnList(project, RangeNull, ref _userStoriesNone, ScrumData);
                }
                return null;
            }
        }

        /// <summary>
        /// Moves the items in the current list. 
        /// </summary>
        /// <param name="project"></param>
        /// <param name="range"></param>
        /// <param name="listUserStories"></param>
        /// <param name="scrumData"></param>
        /// <returns></returns>
        private ObservableCollection<IScrumItemViewModel<DomainBase>> SetUserStoriesOnList(Project project, Range range, ref ObservableCollection<IScrumItemViewModel<DomainBase>> listUserStories, ScrumViewModelData scrumData)
        {
            var stories1 = from x in project.Stories
                           where range.IsInRange(x.Ranking) && !ElementManager.IsClosedState(x.State) && scrumData.ContainsFilter(x)
                           select (IScrumItemViewModel<DomainBase>)new UserStoryViewModel(x);

            var stories2 = from x in project.Iterations
                           from y in x.Stories
                           where range.IsInRange(y.Ranking) && !ElementManager.IsClosedState(y.State) && scrumData.ContainsFilter(y)
                           select (IScrumItemViewModel<DomainBase>)new UserStoryViewModel(y);

            ObservableCollection<IScrumItemViewModel<DomainBase>> userStories;
            if (_scrumData.ConfigManager.Config.IsBugUserStory)
            {
                var bugs1 = from x in project.Bugs
                            where range.IsInRange(x.Ranking) && !ElementManager.IsClosedState(x.State) && scrumData.ContainsFilter(x)
                            select (IScrumItemViewModel<DomainBase>) new BugViewModel(x);

                var bugs2 = from x in project.Iterations
                            from y in x.Bugs
                            where range.IsInRange(y.Ranking) && !ElementManager.IsClosedState(y.State) && scrumData.ContainsFilter(y)
                            select (IScrumItemViewModel<DomainBase>) new BugViewModel(y);
                userStories = new ObservableCollection<IScrumItemViewModel<DomainBase>>( stories1.Union(stories2).Union(bugs1).Union(bugs2));
            }
            else
            {
                userStories = new ObservableCollection<IScrumItemViewModel<DomainBase>>(stories1.Union(stories2));
            }
            
            if (listUserStories == null)
            {
                listUserStories = userStories;

            }
            else
            {
                listUserStories.UnionToLeft(userStories);
            }
            listUserStories.SortDomainItems();
            return listUserStories;
        }
    
        #endregion

        #region Constructors / Destructor
        //--------------------------------------------------------------------
        // Constructors / Destructor
        //--------------------------------------------------------------------
    
        #endregion

        #region Methods
        //--------------------------------------------------------------------
        // Methods
        //--------------------------------------------------------------------


        /// <summary>
        /// Gets a stackrank 
        /// </summary>
        /// <param name="range">The range.</param>
        /// <returns></returns>
        public double GetNewStackStackInRange(Range range)
        {
            var project = ScrumData.CurrentProject;
            var stories1 = from x in project.Stories
                           where range.IsInRange(x.Ranking)
                           select (IScrumItemViewModel<DomainBase>)new UserStoryViewModel(x);

            var stories2 = from x in project.Iterations
                           from y in x.Stories
                           where range.IsInRange(y.Ranking) 
                           select (IScrumItemViewModel<DomainBase>)new UserStoryViewModel(y);

            var bugs1 = from x in project.Bugs
                        where range.IsInRange(x.Ranking) 
                        select (IScrumItemViewModel<DomainBase>)new BugViewModel(x);

            var bugs2 = from x in project.Iterations
                        from y in x.Bugs
                        where range.IsInRange(y.Ranking) 
                        select (IScrumItemViewModel<DomainBase>)new BugViewModel(y);

            var userStories = new List<IScrumItemViewModel<DomainBase>>(stories1.Union(stories2).Union(bugs1).Union(bugs2));

            double maxValue = 0;
            if(userStories.Count>0)
            {
                maxValue = userStories.Max((e) => e.Ranking.GetValueOrDefault(0));    
            }
            
            return Math.Max(range.Min, maxValue) + 1;
        }

        /// <summary>
        /// Moves a UserStory in a current stack range position.
        /// </summary>
        /// <param name="story">The story which gets moved.</param>
        /// <param name="stackRankNew">The new stackrank value</param>
        /// <param name="minValue">stack range min value</param>
        /// <param name="maxValue">stack range max value</param>
        public void SetStackRank(IScrumItemViewModel<DomainBase> story, double stackRankNew, double minValue, double maxValue)
        {
            using (ScrumData.DataContext.DisableElementChangedEvents())
            {
                double oldStackRank = story.Ranking.GetValueOrDefault(0);

                //no changes -> leave
                if (story.Ranking.HasValue && story.Ranking.Value == stackRankNew)
                {
                    return;
                }
                //set the rank
                story.Ranking = stackRankNew;

                //get all UserStories and sort by rank
                IEnumerable<IScrumItem> rankingItems = GetAllRankingItems();
                var orderedUserStories = rankingItems.OrderBy(c => c.Ranking.GetValueOrDefault(0));

                //moves this story in the range? if ranking has no value -> from outside the ringe
                if (story.Ranking.HasValue && oldStackRank >= minValue && oldStackRank <= maxValue)
                {
                    MoveStackInRange(story, oldStackRank, stackRankNew, orderedUserStories);
                }
                else
                {
                    //moves a story into the range
                    AddStoryToStackRange(story, stackRankNew, maxValue, orderedUserStories);
                }
            }
        }

      
        protected override void ReleaseManagedResources()
        {
            ScrumData.DataContext.ElementChanged -= OnContextElementChanged;
            ScrumData.DataContext.CollectionChanged -= OnContextCollectionChanged;
            ScrumData.PropertyChanged -= OnScrumDataPropertyChanged;

            DisposeList(_userStoriesNone);
            DisposeList(_userStoriesCould);
            DisposeList(_userStoriesShould);
            DisposeList(_userStoriesMust);

            base.ReleaseManagedResources();
        }

        private void DisposeList(IEnumerable<IScrumItemViewModel<DomainBase>> list)
        {
            if (list != null)
            {
                foreach (var story in list)
                {
                    story.Dispose();
                }
            }
        }

        private IEnumerable<IScrumItem> GetAllRankingItems()
        {
            var project = ScrumData.CurrentProject;
            var stories1 = from x in project.Stories
                           where x.Ranking.HasValue
                           select (IScrumItem)x;

            var stories2 = from x in project.Iterations
                           from y in x.Stories
                           where y.Ranking.HasValue
                           select (IScrumItem)y;


            var bugs1 = from x in project.Bugs
                        where x.Ranking.HasValue
                        select (IScrumItem)x;

            var bugs2 = from x in project.Iterations
                        from y in x.Bugs
                        where y.Ranking.HasValue
                        select (IScrumItem)y;

            return new List<IScrumItem>(stories1.Union(stories2)).Union(bugs1).Union(bugs2);
        }

        /// <summary>
        /// Adds to all stack-ranks +1 until a free space is found. if max range value is reached all other stories gets the same stack rank.
        /// </summary>
        private void AddStoryToStackRange(IScrumItemViewModel<DomainBase> story, double stackRankNew, double maxValue, IEnumerable<IScrumItem> orderedUserStories)
        {
            var stories = from c in orderedUserStories where c.Ranking.Value >= stackRankNew && c.Ranking.Value < maxValue select c;
            double stackRank = stackRankNew;

            foreach (var x in stories)
            {
                if (Equals(x, story.Original)) { continue; }
                stackRank++;
                if (stackRank < x.Ranking)
                {
                    break;
                }
                x.Ranking = Math.Min(maxValue, stackRank);
            }
        }

        /// <summary>
        /// Moves the story in the range (Backwards and Fowards)
        /// </summary>
        /// <param name="story"></param>
        /// <param name="oldStackRank"></param>
        /// <param name="stackRankNew"></param>
        /// <param name="orderedUserStories"></param>
        private void MoveStackInRange(IScrumItemViewModel<DomainBase> story, double oldStackRank, double stackRankNew, IEnumerable<IScrumItem> orderedUserStories)
        {
            //the min-max range
            double minRank = Math.Min(oldStackRank, stackRankNew);
            double maxRank = Math.Max(oldStackRank, stackRankNew);
            
            //gets all stories in this range
            var stories = from c in orderedUserStories
                          where c.Ranking.Value >= minRank && c.Ranking.Value <= maxRank
                          select c;

            //store the minRank 
            double tmpMinRank = minRank;

            //if the item has move fowards all item has to move one step backwards 
            if (oldStackRank == tmpMinRank)
            {
                tmpMinRank -= 1;
            }
            foreach (var x in stories)
            {
                if (Equals(x, story.Original))
                {
                    continue;
                }
                tmpMinRank++;
                x.Ranking = tmpMinRank;
            }
        }


        #endregion

        #region Events
        //--------------------------------------------------------------------
        // Events
        //--------------------------------------------------------------------

        private void OnScrumDataPropertyChanged(object sender, PropertyChangedEventArgs e)
        {

            if (e.PropertyName == "Filter")
            {
                SendPropertyChanged("UserStoriesMust");
                SendPropertyChanged("UserStoriesShould");
                SendPropertyChanged("UserStoriesCould");
                SendPropertyChanged("UserStoriesNone");
            }
        }

        private void OnContextElementChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e is DomainChangedEventArgs && ((DomainChangedEventArgs)e).ContainsPropertyNames("State", "Ranking")) 
            {
                SendPropertyChanged("UserStoriesMust");
                SendPropertyChanged("UserStoriesShould");
                SendPropertyChanged("UserStoriesCould");
                SendPropertyChanged("UserStoriesNone");
            }
        }

        private void OnContextCollectionChanged(object sender, DomainCollectionChangedEventArgs e)
        {
            SendPropertyChanged("UserStoriesMust");
            SendPropertyChanged("UserStoriesShould");
            SendPropertyChanged("UserStoriesCould");
            SendPropertyChanged("UserStoriesNone");
        }
        #endregion
    }
 
}